
var XML = {
    parse: function (txt) {
        var parser = {};
        parser.stack = [
            {
                p: -1, //parent index
                i: 0, //index
                n: null, //tag name
                l: 0, //position of '<'
                r: txt.length, //position of '>'
                c: 0, //count of children
            }
        ];
        parser.begin = 0; //current position
        parser.level = 0;
        parser.index = 0; //current index
        parser.array = [];
        XML.parseLT.call(parser, txt, 0, txt.length);
        //normalize nodes
        parser.array.sort(function (item1, item2) {
            return item1.index - item2.index;
        });
        //return parser.array;
        return {
            list: parser.array,
            toString: function () {
                return XML.stringify(this.list);
            },
            getNode: function (tag) {
                for (var i = 0, l = this.list.length; i < l; ++i) {
                    var item = this.list[i];
                    if (item.tag == tag) {
                        return item;
                    }
                }
                return null;
            },
            getText: function (tag, def) {
                for (var i = 0, l = this.list.length; i < l; ++i) {
                    var item = this.list[i];
                    if (item.tag == tag) {
                        return item.text;
                    }
                }
                return def;
            },
            getList: function (tag) {
                var result = [];
                var parent = -1;
                for (var i = 0, l = this.list.length; i < l; ++i) {
                    var item = this.list[i];
                    if (item.tag == tag) {
                        parent = item.index;
                        result.unshift({});
                    } else if (item.parent == parent && item.length == 0) {
                        result[0][item.tag] = item.text;
                    }
                }
                result.reverse();
                return result;
            },
            foreach: function (list, call) {
                for (var i = 0, l = list.length; i < l; ++i) {
                    call(list[i]);
                }
            }
        }
    },
    parseLT: function (t, b, e) {
        var p = t.indexOf('<', b);
        if (p < 0) {
            return;
        }
        this.begin = p;
        if (p < e) {
            XML.parseGT.call(this, t, p + 1, e);
        }
    },
    parseGT: function (t, b, e) {
        var p = t.indexOf('>', b);
        if (p < 0) {
            return;
        }
        XML.parseTag.call(this, t, t.substr(b, p - b), p);
        if (p < e) {
            XML.parseLT.call(this, t, p + 1, e);
        }
    },
    parseTag: function (t, n, p) {
        if (n.slice(-1) == "/") {
            //<tag/>
            this.stack[0].c += 1;
            this.array.push({
                parent: this.stack[0].i,
                index: ++this.index,
                level: this.level,
                tag: n.slice(0, -1).trim(),
                text: "",
                length: 0
            })
        } else if (n[0] != "/") {
            //<tag>
            this.level += 1;
            this.stack[0].c += 1;
            //first node is the last node
            this.stack.unshift({
                p: this.stack[0].i,
                i: ++this.index,
                n: n,
                l: this.begin,
                r: p + 1,
                c: 0
            });
        } else {
            //</tag>
            this.level -= 1;
            var node = this.stack.shift();
            this.array.push({
                parent: node.p,
                index: node.i,
                level: this.level,
                tag: n.slice(1).trim(), //tail tag has no attributes
                text: node.c > 0 ? "" : t.substr(node.r, this.begin - node.r), //only leaf node has text
                length: node.c
            })
        }
    },
    stringify: function (array) {
        var result = [];
        result.push("level parent index length tag text");
        var pre = [
            "",
            "    ",
            "        ",
            "            ",
            "                ",
            "                    ",
            "                        ",
            "                            ",
            "                                "
        ];
        array.forEach(function (item) {
            var line = [];
            line.push(item.level);
            line.push(("00" + item.parent).substr(-3));
            line.push(("00" + item.index).substr(-3));
            line.push(item.length);
            line.push(item.tag);
            if (item.length == 0) {
                line.push(item.text);
            }
            result.push(pre[item.level] + "[" + line.join("] [") + "]");
        });
        return result.join("\n");
    },
    format: function (xml) {
        var result = "";
        for (i = 0, l = -1; i < xml.length; ++i) {
            if (xml[i] == "<") {
                //<tag
                if (xml[i + 1] != "/") {
                    ++l;
                }
                //tag><tag
                if (xml[i - 1] == ">") {
                    //tag/>
                    if (xml[i - 2] == "/") {
                        --l;
                    }
                    for (j = 0; j < l; ++j) {
                        result += "    ";
                    }
                }
                //</tag
                if (xml[i + 1] == "/") {
                    --l;
                }
            }
            result += xml[i];
            //tag><tag
            if (xml[i] == ">" && xml[i + 1] == "<") {
                result += "\n";
            }
        }
        return result;
    },
    o2x: function (o) {
        var r = [];
        for (var n in o) {
            var v = o[n];
            if (v instanceof Array) {
                r.push("<" + n + ">");
                v.forEach(function (item) { r.push(XML.o2x(item)) });
                r.push("</" + n + ">");
            } else if (typeof v == "object") {
                r.push("<" + n + ">");
                r.push(XML.o2x(v));
                r.push("</" + n + ">");
            } else {
                if (v) {
                    r.push("<" + n + ">" + v + "</" + n + ">");
                } else {
                    r.push("<" + n + "/>");
                }
            }
        }
        return r.join("");
    },
    x2o: function (x, m) {
        var r = {};
        var s = [];
        s.push({
            node: r,
            tag: "",
            level: -1,
            index: 0
        });
        XML.parse(x).list.forEach(function (n) {
            while (s.length > 1 && s[0].level >= n.level) {
                s.shift();
            }
            //output("[" + s[0].level + "] [" + n.level + "] [" + s[0].tag + "] [" + n.tag + "]");
            if (n.parent != s[0].index) {
                //
            } else if (n.length > 0) {
                if (m && n.tag in m) {
                    var i = 0;
                    if (!m[n.tag].length) {
                        s[0].node[n.tag] = [{}];
                    } else {
                        i = s[0].node[n.tag].push({}) - 1;
                    }
                    s.unshift({
                        node: s[0].node[n.tag][i],
                        tag: n.tag,
                        level: n.level,
                        index: n.index
                    });
                    m[n.tag].push(s[0].node);
                } else {
                    s[0].node[n.tag] = {};
                    s.unshift({
                        node: s[0].node[n.tag],
                        tag: n.tag,
                        level: n.level,
                        index: n.index
                    });
                }
            } else {
                s[0].node[n.tag] = n.text;
            }
        })
        return r;
    }
}
